using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;

using Derm.Render;

namespace ShaderGen.Ui
{
	public partial class MainWindow : Form
	{
		#region Constructors

		/// <summary>
		/// Construct a MainWindow.
		/// </summary>
		public MainWindow()
		{
			InitializeComponent();

			// Create contextes
			CreateRenderContext();
			// Initialize language combos (required contextes)
			InitializeGlobalLanguageCombos();

			ShaderImageList.Images.Add("vertex-shader", Image.FromFile("Resources/VertexShader.ico"));
			ShaderImageList.Images.Add("shader-include", Image.FromFile("Resources/ShaderInclude.ico"));
			ShaderImageList.Images.Add("shader-include-group", Image.FromFile("Resources/ShaderIncludeGroup.ico"));
			ShaderImageList.Images.Add("shader-object", Image.FromFile("Resources/ShaderObject.ico"));
			ShaderImageList.Images.Add("shader-object-group", Image.FromFile("Resources/ShaderObjectGroup.ico"));

			// All other UI shall be aligned to empty project
			UpdateMenuBar();
		}

		#endregion

		#region Current Project

		/// <summary>
		/// Ask to the user how manage current project, in the case is modified.
		/// </summary>
		/// <returns>
		/// It returns a boolean value indicating whether the current project can be discarded.
		/// </returns>
		private bool DiscardCurrentProject()
		{
			if ((mCurrentProject != null) && (mCurrentProjectChanged == true)) {
				DialogResult res = MessageBox.Show(
					"Current project has been modified. Do you want save changes before closing it?", "Question...",
				    MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1
					);

				// Save project if requested
				if (res == DialogResult.Yes) {
					try {
						MenuSaveProject_Click(MenuSaveProject, EventArgs.Empty);
					} catch(Exception exception) {
						res = MessageBox.Show(
							"Unable to save current project: " + exception.Message + ". Do you still want close current project?", "Error...",
							MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1
							);

						return (res == DialogResult.Yes);
					}
				}
					

				return (res != DialogResult.Cancel);
			} else
				return (true);
		}

		/// <summary>
		/// Set the current project. All UI is updated consequently.
		/// </summary>
		/// <param name="project"></param>
		/// <param name="projectPath"></param>
		private void SetCurrentProject(ShaderLibraryProject project, string projectPath)
		{
			// Store project properties
			mCurrentProject = project;
			mCurrentProjectPath = projectPath;
			// Reset modified flag
			NotifyProjectChanged(false);

			// Update UI
			CloseAllTabs();
			UpdateModulesTree();
		}

		private bool CreateProjectDirectory(string path)
		{
			if (path == null)
				throw new ArgumentNullException("path");

			try {
				Directory.CreateDirectory(path);
			} catch (IOException ioException) {
				MessageBox.Show("Unable to create directory: " + ioException.Message + ".", "Created directory failed...", MessageBoxButtons.OK, MessageBoxIcon.Information);
				return (false);
			}

			return (true);
		}

		private bool RenameProjectDirectory(string oldPath, string newPath)
		{
			if (oldPath == null)
				throw new ArgumentNullException("oldPath");
			if (newPath == null)
				throw new ArgumentNullException("newPath");

			// Renamed directory
			try {
				Directory.Move(oldPath, newPath);
			} catch (IOException ioException) {
				MessageBox.Show("Unable to rename directory: " + ioException.Message + ".", "Rename failed...", MessageBoxButtons.OK, MessageBoxIcon.Information);
				return (false);
			}

			// Update project libraries
			foreach (ShaderLibraryConfiguration libraryConfiguration in mCurrentProject.Libraries) {
				// Library configuration contained in renamed path?
				if (libraryConfiguration.ConfigurationPath.StartsWith(oldPath) == true)
					libraryConfiguration.ConfigurationPath = Path.Combine(newPath, libraryConfiguration.ConfigurationPath.Substring(oldPath.Length));

				foreach (ShaderLibraryModule libraryModule in libraryConfiguration.Modules) {
					string absModulePath = Path.Combine(libraryConfiguration.ConfigurationPath, libraryModule.Path);

					if (absModulePath.StartsWith(oldPath)) {
						absModulePath = Path.Combine(newPath, absModulePath.Substring(oldPath.Length));
						absModulePath = ShaderLibraryProject.GetRelativePath(absModulePath, libraryConfiguration.ConfigurationPath);
						libraryModule.Path = absModulePath;
					}
				}
			}

			// Update UI
			UpdateModulesTree();

			return (true);
		}

		/// <summary>
		/// Set the modified flag for the current project.
		/// </summary>
		/// <param name="flag"></param>
		private void NotifyProjectChanged(bool flag)
		{
			// Store modified flag
			mCurrentProjectChanged = flag;
			// Update UI
			UpdateWindow();
			UpdateMenuBar();
		}

		/// <summary>
		/// Current shader library project loaded.
		/// </summary>
		private ShaderLibraryProject mCurrentProject;

		/// <summary>
		/// The current project path.
		/// </summary>
		private string mCurrentProjectPath;

		/// <summary>
		/// Flag indicating whether the project has been modified since last load/save operation.
		/// </summary>
		private bool mCurrentProjectChanged;

		#endregion

		#region Shader Compilation

		/// <summary>
		/// Create all render context used for compilation.
		/// </summary>
		private void CreateRenderContext()
		{
			try {
				mRenderWindow = new RenderWindow(this);
				mRenderWindow.Create(new RenderSurfaceFormat(Derm.Pixel.Type.RGB24));
				mRenderContext = new RenderContext(RenderContext.GLVersion.Current, mRenderWindow.GetDeviceContext(), null);
			} catch (Exception exception) {
				MessageBox.Show("Unable to create any OpenGL context (" + exception.Message + ").", "Fatal...", MessageBoxButtons.OK, MessageBoxIcon.Error);
				Application.Exit();
			}

			mContexts[mRenderContext.Version] = mRenderContext;

			foreach (RenderContext.GLVersion glVersion in Enum.GetValues(typeof(RenderContext.GLVersion))) {
				if (mContexts.ContainsKey(glVersion) == false) {
					try {
						RenderContext versionContext = new RenderContext(glVersion, mRenderWindow.GetDeviceContext(), null);

						if (mContexts.ContainsKey(versionContext.Version) == false)
							mContexts[versionContext.Version] = versionContext;	
						else
							versionContext.Dispose();
					} catch (Exception exception) {
						
					}
				}
			}
		}

		/// <summary>
		/// Used for getting device context. Required for instancing render contextes.
		/// </summary>
		private RenderWindow mRenderWindow;

		/// <summary>
		/// Collection of contextes for different OpenGL versions.
		/// </summary>
		private readonly Dictionary<RenderContext.GLVersion, RenderContext> mContexts = new Dictionary<RenderContext.GLVersion, RenderContext>();

		/// <summary>
		/// The default render context.
		/// </summary>
		private RenderContext mRenderContext;

		/// <summary>
		/// Currently selected render context.
		/// </summary>
		private RenderContext mCurrentGlobalContext;

		#endregion

		#region This Window Management

		private void UpdateWindow()
		{
			StringBuilder sb = new StringBuilder();

			sb.Append("Shader Library Composer");
			if (mCurrentProject != null) {
				sb.AppendFormat(" - {0}", Path.GetFileNameWithoutExtension(mCurrentProjectPath));
				if (mCurrentProjectChanged == true)
					sb.Append(" *");
				sb.AppendFormat(" ({0})", Path.GetDirectoryName(mCurrentProjectPath));
			}

			Text = sb.ToString();
		}

		private void MainWindow_FormClosing(object sender, FormClosingEventArgs e)
		{
			switch (e.CloseReason) {
				case CloseReason.UserClosing:
				case CloseReason.TaskManagerClosing:
				case CloseReason.ApplicationExitCall:		// Here we are!
					e.Cancel = !DiscardCurrentProject();
					break;
				default:
					// Automatically save project (with error notification)
					MenuSaveProject_Click(MenuSaveProject, EventArgs.Empty);
					break;
			}
		}

		#endregion

		#region OpenGL Version Combo Boxes

		private class CmbGlVersionItem
		{
			public CmbGlVersionItem(RenderContext context)
			{
				Context = context;
			}

			public RenderContext Context;

			private static string GetGlVersionString(RenderContext.GLVersion version)
			{
				Match versionMatch = sGlVersionRegex.Match(version.ToString());

				if (versionMatch.Success) {
					StringBuilder sb = new StringBuilder();

					sb.AppendFormat("OpenGL {0}.{1}", versionMatch.Groups["Major"], versionMatch.Groups["Minor"]);

					if (String.IsNullOrEmpty(versionMatch.Groups["Revision"].Value) == false)
						sb.AppendFormat(".{0}", versionMatch.Groups["Revision"]);

					return (sb.ToString());
				} else
					return (version.ToString());
			}

			public override string ToString()
			{
				return (GetGlVersionString(Context.Version));
			}

			private static readonly Regex sGlVersionRegex = new Regex(@"Version_(?<Major>\d)_(?<Minor>\d)[_(?<Revision>\d)]?");
		}

		private class CmbGlslVersionItem
		{
			public CmbGlslVersionItem(RenderContext.GLSLVersion glslVersion)
			{
				Version = glslVersion;
			}

			public RenderContext.GLSLVersion Version;

			private static string GetGlVersionString(RenderContext.GLSLVersion version)
			{
				Match versionMatch = sGlslVersionRegex.Match(version.ToString());

				if (versionMatch.Success) {
					StringBuilder sb = new StringBuilder();

					sb.AppendFormat("Shading Language {0}.{1}", versionMatch.Groups["Major"], versionMatch.Groups["Minor"]);

					return (sb.ToString());
				} else
					return (version.ToString());
			}

			public override string ToString()
			{
				if (Version != RenderContext.GLSLVersion.None)
					return (GetGlVersionString(Version));
				else
					return ("All supported");
			}

			private static readonly Regex sGlslVersionRegex = new Regex(@"Version_(?<Major>\d)_(?<Minor>\d)");
		}

		private void InitializeGlobalLanguageCombos()
		{
			foreach (RenderContext.GLVersion glVersion in Enum.GetValues(typeof(RenderContext.GLVersion))) {
				RenderContext context;

				if (mContexts.TryGetValue(glVersion, out context))
					CmbGlobalLanguage.Items.Add(new CmbGlVersionItem(context));
			}
			CmbGlobalLanguage.SelectedIndexChanged += delegate { mCurrentGlobalContext = ((CmbGlVersionItem)CmbGlobalLanguage.SelectedItem).Context; };
			CmbGlobalLanguage.SelectedIndexChanged += delegate { UpdateGlobalGlslLanguage(); };
			CmbGlobalLanguage.SelectedIndex = CmbGlobalLanguage.Items.Count - 1;
		}

		private void UpdateGlobalGlslLanguage()
		{
			CmbGlobalShaderVersion.Items.Clear();
			foreach (RenderContext.GLSLVersion glslVersion in Enum.GetValues(typeof(RenderContext.GLSLVersion))) {
				if ((glslVersion <= mCurrentGlobalContext.ShadingVersion) && (glslVersion != RenderContext.GLSLVersion.None) && (glslVersion != RenderContext.GLSLVersion.Current)) {
					CmbGlobalShaderVersion.Items.Add(new CmbGlslVersionItem(glslVersion));
				}
			}
			CmbGlobalShaderVersion.Items.Add(new CmbGlslVersionItem(RenderContext.GLSLVersion.None));
			CmbGlobalShaderVersion.SelectedIndex = CmbGlobalShaderVersion.Items.Count - 1;
		}

		#endregion

		#region TreeView Management

		private void ModulesTree_BeforeLabelEdit(object sender, NodeLabelEditEventArgs e)
		{
			// By default, all nodes can be renamed
			e.CancelEdit = false;
		}

		private void ModulesTree_AfterLabelEdit(object sender, NodeLabelEditEventArgs e)
		{
			if (e.Node.Tag is String) {
				// Directory node case

				TreeNode libraryNode = GetShaderLibraryNode(e.Node);
				string path = (string) e.Node.Tag;

				// Path is relative to the shader library configuration
				ShaderLibraryConfiguration libraryConfiguration = (ShaderLibraryConfiguration) libraryNode.Tag;

				path = Path.Combine(Path.GetDirectoryName(libraryConfiguration.ConfigurationPath), path);

				// Does the path exist?
				if (Directory.Exists(path) == true) {
					string parentPath = (e.Node.Parent != null) ? (string) e.Node.Parent.Tag : String.Empty;
					string newpath = Path.Combine(Path.GetDirectoryName(libraryConfiguration.ConfigurationPath), parentPath);

					// Rename an existing directory
					if (RenameProjectDirectory(path, Path.Combine(newpath, e.Label)) == false)
						e.CancelEdit = true;
				} else {
					string parentPath = (e.Node.Parent != null) ? (string) e.Node.Parent.Tag : String.Empty;

					path = Path.Combine(Path.GetDirectoryName(libraryConfiguration.ConfigurationPath), parentPath);
					path = Path.Combine(path, e.Label);

					// Create a new directory
					CreateProjectDirectory(path);
					UpdateModulesTree();
				}
			} else
				e.CancelEdit = true;		// Unrecognized node: do not edit
		}

		private void ModulesTree_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
		{
			object obj = e.Node.Tag;

			if        (obj is ShaderLibraryProgram) {
				TreeNode libraryNode = GetShaderLibraryNode(e.Node);

				if (libraryNode.Tag is ShaderLibraryConfiguration) {
					OpenShaderProgram((ShaderLibraryProgram)obj);
				}
			} else if (obj is ShaderLibraryObject) {
				TreeNode libraryNode = GetShaderLibraryNode(e.Node);

				if (libraryNode.Tag is ShaderLibraryConfiguration) {
					OpenShaderSource((ShaderLibraryConfiguration)libraryNode.Tag, ((ShaderLibraryObject)obj).SourcePath);	
				}
			} else if (obj is ShaderLibraryInclude) {
				TreeNode libraryNode = GetShaderLibraryNode(e.Node);

				if (libraryNode.Tag is ShaderLibraryConfiguration) {
					OpenShaderSource((ShaderLibraryConfiguration) libraryNode.Tag, ((ShaderLibraryInclude)obj).SourcePath);
				}
			} else if (obj is String) {
				TreeNode libraryNode = GetShaderLibraryNode(e.Node);

				if (libraryNode.Tag is ShaderLibraryConfiguration) {
					ShaderLibraryConfiguration libraryConfiguration = (ShaderLibraryConfiguration) libraryNode.Tag;
					string fileAbsolutePath = Path.Combine(libraryConfiguration.ConfigurationPath, (string)obj);

					if (Directory.Exists(fileAbsolutePath) == false)
						OpenShaderSource(libraryConfiguration, (string)obj);
				}
			}
		}

		private void UpdateModulesTree()
		{
			ModulesTree.BeginUpdate();
			ModulesTree.Nodes.Clear();

			if (mCurrentProject == null)
				return;

			// Populate by library
			foreach (ShaderLibraryConfiguration shaderLibrary in mCurrentProject.Libraries) {
				TreeNode libraryNode = CreateShaderLibraryNode(shaderLibrary);

				Array.Sort(shaderLibrary.Modules, delegate(ShaderLibraryModule a, ShaderLibraryModule b) {
            		return (a.Path.CompareTo(b.Path));
            	});

				// Directory definition
				string[] directories = Directory.GetDirectories(Path.GetDirectoryName(shaderLibrary.ConfigurationPath), "*", SearchOption.AllDirectories);

				foreach (string directoryPath in directories) {
					string relativeDir = ShaderLibraryProject.GetRelativePath(Path.GetDirectoryName(shaderLibrary.ConfigurationPath), directoryPath);

					if (relativeDir.Contains(".svn"))
						continue;
					if (relativeDir.Contains("bin"))
						continue;
					if (relativeDir.Contains("obj"))
						continue;
					GetShaderTreeNode(libraryNode, relativeDir + "\\");
				}

				// C# source definition
				string[] csFiles = Directory.GetFiles(Path.GetDirectoryName(shaderLibrary.ConfigurationPath), "*.cs", SearchOption.AllDirectories);

				foreach (string csFilePath in csFiles) {
					string relativeDir = ShaderLibraryProject.GetRelativePath(Path.GetDirectoryName(shaderLibrary.ConfigurationPath), csFilePath);
					TreeNode moduleNode = CreateShaderLibraryModuleNode(shaderLibrary, relativeDir);
					TreeNode parentNode = GetShaderTreeNode(libraryNode, relativeDir);

					parentNode.Nodes.Add(moduleNode);
				}

				// Module definition
				ModulesTree.Nodes.Add(libraryNode);
				foreach (ShaderLibraryModule libraryModule in shaderLibrary.Modules) {
					TreeNode moduleDirNode = GetShaderTreeNode(libraryNode, libraryModule.Path);
					TreeNode moduleNode = CreateShaderLibraryModuleNode(shaderLibrary, libraryModule);

					if (libraryModule.ShaderPrograms != null) {
						TreeNode groupNode = new TreeNode("Programs");
						foreach (ShaderLibraryProgram shaderProgram in libraryModule.ShaderPrograms)
							groupNode.Nodes.Add(CreateShaderLibraryProgramNode(shaderProgram));
						groupNode.ImageKey = groupNode.SelectedImageKey = "shader-program-group";
						moduleNode.Nodes.Add(groupNode);
					}
					if (libraryModule.ShaderObjects != null) {
						TreeNode groupNode = new TreeNode("Shaders");
						foreach (ShaderLibraryObject shaderObject in libraryModule.ShaderObjects)
							groupNode.Nodes.Add(CreateShaderLibraryObjectNode(shaderObject));
						groupNode.ImageKey = groupNode.SelectedImageKey = "shader-object-group";
						moduleNode.Nodes.Add(groupNode);
					}
					if (libraryModule.ShaderIncludes != null) {
						TreeNode groupNode = new TreeNode("Includes");
						foreach (ShaderLibraryInclude shaderInclude in libraryModule.ShaderIncludes)
							groupNode.Nodes.Add(CreateShaderLibraryIncludeNode(shaderInclude));
						groupNode.ImageKey = groupNode.SelectedImageKey = "shader-include-group";
						moduleNode.Nodes.Add(groupNode);
					}

					moduleDirNode.Nodes.Add(moduleNode);
				}


			}

			ModulesTree.ExpandAll();
			if (ModulesTree.Nodes.Count > 0)
				ModulesTree.Nodes[0].EnsureVisible();

			ModulesTree.EndUpdate();
		}

		private TreeNode CreateShaderLibraryNode(ShaderLibraryConfiguration shaderLibrary)
		{
			string nodeText = String.Format("{0} ({1})",
				Path.GetFileNameWithoutExtension(shaderLibrary.ConfigurationPath),
				Path.GetDirectoryName(shaderLibrary.ConfigurationPath)
				);
			
			TreeNode node = new TreeNode(nodeText);

			// Associate context menu
			node.ContextMenuStrip = CtxMenuDirectoryNode;
			// Link to the object
			node.Tag = shaderLibrary;

			return (node);
		}

		private TreeNode CreateShaderLibraryModuleNode(ShaderLibraryConfiguration shaderConfiguration, ShaderLibraryModule module)
		{
			string nodeText = Path.GetFileNameWithoutExtension(module.Path);

			TreeNode node = new TreeNode(nodeText);

			LoadShaderTreeIcon(shaderConfiguration, module.Path);
			node.ImageKey = node.SelectedImageKey = module.Path;
			// Associate context menu
			node.ContextMenuStrip = CtxMenuModuleNode;
			// Link to the object
			node.Tag = module;

			return (node);
		}

		private TreeNode CreateShaderLibraryModuleNode(ShaderLibraryConfiguration shaderConfiguration, string path)
		{
			string nodeText = Path.GetFileNameWithoutExtension(path);

			TreeNode node = new TreeNode(nodeText);

			LoadShaderTreeIcon(shaderConfiguration, path);
			node.ImageKey = node.SelectedImageKey = path;
			// Associate context menu
			node.ContextMenuStrip = CtxMenuModuleNode;
			// Link to the object
			node.Tag = path;

			return (node);
		}

		private TreeNode CreateShaderLibraryObjectNode(ShaderLibraryObject shaderObject)
		{
			string nodeText = Path.GetFileName(shaderObject.SourcePath);

			TreeNode node = new TreeNode(nodeText);

			// Node image
			node.ImageKey = node.SelectedImageKey = "shader-object";
			// Associate context menu
			node.ContextMenuStrip = CtxMenuShaderObjectNode;
			// Link to the object
			node.Tag = shaderObject;

			return (node);
		}

		private TreeNode CreateShaderLibraryIncludeNode(ShaderLibraryInclude shaderInclude)
		{
			TreeNode node = new TreeNode(shaderInclude.ClassName);

			// Node image
			node.ImageKey = node.SelectedImageKey = "shader-include";
			// Associate context menu
			node.ContextMenuStrip = CtxMenuShaderObjectNode;
			// Link to the object
			node.Tag = shaderInclude;

			return (node);
		}

		private TreeNode CreateShaderLibraryProgramNode(ShaderLibraryProgram shaderProgram)
		{
			TreeNode node = new TreeNode(shaderProgram.ClassName);

			// Associate context menu
			node.ContextMenuStrip = CtxMenuShaderObjectNode;
			// Link to the object
			node.Tag = shaderProgram;

			return (node);
		}

		/// <summary>
		/// Get a TreeNode representing the specified path.
		/// </summary>
		/// <param name="shaderLibraryNode"></param>
		/// <param name="path"></param>
		/// <returns></returns>
		private TreeNode GetShaderTreeNode(TreeNode shaderLibraryNode, string path)
		{
			if (shaderLibraryNode == null)
				throw new ArgumentNullException("shaderLibraryNode");
			if (path == null)
				throw new ArgumentNullException("path");

			// Path really is a file, but we need directory path
			if ((path = Path.GetDirectoryName(path)) == null)
				throw new ArgumentException("no recognized path", "path");
			// Always use forward slash
			path = path.Replace('\\', '/') + "/";

			// Search node by key
			StringBuilder sb = new StringBuilder();
			string[] pathDirNames = path.Split(new char[] {'/'}, StringSplitOptions.RemoveEmptyEntries);

			ShaderLibraryConfiguration shaderConfiguration = (ShaderLibraryConfiguration) shaderLibraryNode.Tag;

			foreach (string dirName in pathDirNames) {
				sb.AppendFormat("{0}/", dirName);

				string currentPath = sb.ToString();

				if (shaderLibraryNode.Nodes.ContainsKey(currentPath) == false) {
					TreeNode dirNode = shaderLibraryNode.Nodes.Add(sb.ToString(), dirName);

					// Load directory icon
					LoadShaderTreeIcon(shaderConfiguration, currentPath);
					dirNode.ImageKey = dirNode.SelectedImageKey = currentPath;
					// Associate context menu
					dirNode.ContextMenuStrip = CtxMenuDirectoryNode;
					// Link to object (relative path from shader library configuration)
					dirNode.Tag = sb.ToString();
					// Continue directory tree traversal
					shaderLibraryNode = dirNode;
				} else
					shaderLibraryNode = shaderLibraryNode.Nodes[currentPath];

				if (currentPath == path)
					break;
			}

			return (shaderLibraryNode);
		}

		private TreeNode GetShaderLibraryNode(TreeNode genericNode)
		{
			while (genericNode.Parent != null)
				genericNode = genericNode.Parent;
			return (genericNode);
		}

		private void LoadShaderTreeIcon(ShaderLibraryConfiguration shaderConfiguration, string path)
		{
			if (ShaderImageList.Images.ContainsKey(path) == false) {
				Shfileinfo shFileInfo = new Shfileinfo();
				string dirPath = Path.GetDirectoryName(shaderConfiguration.ConfigurationPath);

				dirPath = Path.Combine(dirPath, path);
				dirPath = dirPath.Replace('/', '\\');
				if (dirPath.EndsWith("\\"))
					dirPath = dirPath.Substring(0, dirPath.Length - 1);

				SHGetFileInfo(dirPath, 0, ref shFileInfo, (uint)Marshal.SizeOf(shFileInfo), SHGFI_ICON|SHGFI_LARGEICON);

				if (shFileInfo.hIcon != IntPtr.Zero) {
					Icon dirIcon = Icon.FromHandle(shFileInfo.hIcon);

					ShaderImageList.Images.Add(path, dirIcon);
				}
			}
		}

		private Dictionary<string, Image> mImages = new Dictionary<string, Image>();
		
		[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
		struct Shfileinfo {
			public IntPtr hIcon;
			public int iIcon;
			public uint dwAttributes;
			[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
			public string szDisplayName;
			[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
			public string szTypeName;
		}

		private const uint SHGFI_ICON = 0x100;
		private const uint SHGFI_LARGEICON = 0x0; // 'Large icon
		private const uint SHGFI_SMALLICON = 0x1; // 'Small icon

		[DllImport("shell32.dll", CharSet=CharSet.Auto)]
		private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref Shfileinfo psfi, uint cbFileInfo, uint uFlags);

		#endregion

		#region Editor Management

		private void CloseAllTabs()
		{
			ShaderEditorTabs.TabPages.Clear();
		}

		private void OpenShaderProgram(ShaderLibraryProgram libraryProgram)
		{
			TabPage objectPage;

			// Add page if necessary
			if (ShaderEditorTabs.TabPages.ContainsKey(libraryProgram.ClassName) == false) {
				ProgramEditorBox programEditorBox = new ProgramEditorBox(mCurrentProject);
				programEditorBox.LibraryProgram = libraryProgram;
				programEditorBox.Dock = DockStyle.Fill;

				ShaderEditorTabs.TabPages.Add(libraryProgram.ClassName, libraryProgram.ClassName);

				objectPage = ShaderEditorTabs.TabPages[libraryProgram.ClassName];
				objectPage.Controls.Add(programEditorBox);
			} else
				objectPage = ShaderEditorTabs.TabPages[libraryProgram.ClassName];

			ShaderEditorTabs.SelectedTab = objectPage;
		}

		private void OpenShaderSource(ShaderLibraryConfiguration shaderConfiguration, string path)
		{
			TabPage objectPage;

			// Add page if necessary
			if (ShaderEditorTabs.TabPages.ContainsKey(path) == false) {
				string shaderPath = Path.Combine(Path.GetDirectoryName(shaderConfiguration.ConfigurationPath), path);

				try {
					ShaderEditorControl editorControl = new ShaderEditorControl(shaderPath);

					editorControl.Dock = DockStyle.Fill;

					ShaderEditorTabs.TabPages.Add(path, Path.GetFileName(path));
					objectPage = ShaderEditorTabs.TabPages[path];
					objectPage.Controls.Add(editorControl);
				} catch (Exception exception) {
					MessageBox.Show(String.Format("Unable to open {0}: {1}.", shaderPath, exception.Message));
					return;
				}
			} else
				objectPage = ShaderEditorTabs.TabPages[path];

			ShaderEditorTabs.SelectedTab = objectPage;
		}

		private void ShaderEditorTabs_MouseClick(object sender, MouseEventArgs e)
		{
			if (e.Button == MouseButtons.Right) {
				TabControl tabControl = (TabControl) sender;
				TabPage tabPage = null;

				for (int i = 0; i < tabControl.TabCount; i++) {
					if (tabControl.GetTabRect(i).Contains(e.Location)) {
						tabPage = tabControl.TabPages[i];
						break;
					}
				}

				if (tabPage != null) {
					if (tabPage.Controls[0] is ShaderTextBox) {
						CtxMenuShaderSourceTab.Tag = tabPage;
						CtxMenuShaderSourceTab.Show((Control) sender, e.Location);
					}
				}
			}
		}

		#endregion

		#region Menu Management

		private void UpdateMenuBar()
		{
			bool hasProject = mCurrentProject != null;
			bool hasProjectModified = hasProject && mCurrentProjectChanged;

			// Project dependent menus
			MenuSaveProject.Enabled = hasProject && hasProjectModified;
			MenuSaveAsProject.Enabled = hasProject && hasProjectModified;
			MenuCloseProject.Enabled = hasProject;

			MenuLibrary.Enabled = hasProject;
			MenuEdit.Enabled = hasProject;
			MenuShader.Enabled = hasProject;
		}

		#endregion

		#region Project Menu Action Implementations

		private void MenuNewProject_Click(object sender, EventArgs e)
		{
			// Warn user about modified project
			if (DiscardCurrentProject() == false)
				return;

			using (FileDialog fileDialog = new SaveFileDialog()) {
				fileDialog.Title = "Create shader library project...";
				fileDialog.Filter = "Shader library project (*.slp)|*.slp|All files|*.*";
				fileDialog.AddExtension = true;
				fileDialog.CheckFileExists = false;

				if (fileDialog.ShowDialog(this) == DialogResult.OK) {
					try {
						ShaderLibraryProject project = new ShaderLibraryProject();

						// Save empty project
						project.Save(fileDialog.FileName);
						// Set current project
						SetCurrentProject(project, fileDialog.FileName);
					} catch (Exception exception) {
						MessageBox.Show(String.Format("Unable to create shader library project to {0}: {1}.", fileDialog.FileName, exception.Message));
					}
				}
			}
		}

		private void MenuOpenProject_Click(object sender, EventArgs e)
		{
			// Warn user about modified project
			if (DiscardCurrentProject() == false)
				return;

			using (FileDialog fileDialog = new OpenFileDialog()) {
				fileDialog.Title = "Open shader library project...";
				fileDialog.Filter = "Shader library project (*.slp)|*.slp|All files|*.*";
				fileDialog.AddExtension = true;
				fileDialog.CheckFileExists = true;

				if (fileDialog.ShowDialog(this) == DialogResult.OK) {
					try {
						// Load a new project
						ShaderLibraryProject project = ShaderLibraryProject.Load(fileDialog.FileName);

						// Load project libraries
						string log;

						project.LoadLibraries(out log);

						if (log != null) {
							// Show warning message
							MessageBox.Show(
								String.Format("Inconsistent project. Here is the issue were found:\n{0}", log), 
								"Incosistent project...", MessageBoxButtons.OK, MessageBoxIcon.Warning
								);
							// Validate project
							// ....
						}

						// Set current project
						SetCurrentProject(project, fileDialog.FileName);
					} catch (Exception exception) {
						StringBuilder sb = new StringBuilder();

						sb.AppendFormat("Unable to load shader library from {0}: {1}.", fileDialog.FileName, exception.Message);

						while (exception.InnerException != null) {
							sb.AppendLine(exception.InnerException.Message);
							exception = exception.InnerException;
						}

						MessageBox.Show(sb.ToString());
					}
				}
			}
		}

		private void MenuSaveProject_Click(object sender, EventArgs e)
		{
			try {
				// Save project
				mCurrentProject.Save(mCurrentProjectPath);
				// Reset modified flag
				NotifyProjectChanged(false);
			} catch (Exception exception) {
				MessageBox.Show(String.Format("Unable to save shader library project to {0}: {1}.", mCurrentProjectPath, exception.Message));
			}
		}

		private void MenuSaveAsProject_Click(object sender, EventArgs e)
		{
			using (FileDialog fileDialog = new SaveFileDialog()) {
				fileDialog.Title = "Save shader library project...";
				fileDialog.Filter = "Shader library project (*.slp)|*.slp|All files|*.*";
				fileDialog.AddExtension = true;
				fileDialog.CheckFileExists = false;

				if (fileDialog.ShowDialog(this) == DialogResult.OK) {
					try {
						// Save project to other file, but don't switch to
						mCurrentProject.Save(fileDialog.FileName);
					} catch (Exception exception) {
						MessageBox.Show(String.Format("Unable to save shader library project to {0}: {1}.", fileDialog.FileName, exception.Message));
					}
				}
			}
		}

		private void MenuCloseProject_Click(object sender, EventArgs e)
		{
			// Warn user about modified project
			if (DiscardCurrentProject() == false)
				return;

			// Reset project
			SetCurrentProject(null, null);
		}

		private void MenuQuitApplication_Click(object sender, EventArgs e)
		{
			Application.Exit();
		}

		#endregion

		#region Library Menu Action Implementations

		private void MenuNewLibrary_Click(object sender, EventArgs e)
		{
			using (FileDialog fileDialog = new SaveFileDialog()) {
				fileDialog.Title = "Create shader library...";
				fileDialog.Filter = "Shader library (*.xml)|*.xml|All files|*.*";
				fileDialog.AddExtension = true;
				fileDialog.CheckFileExists = false;

				if (fileDialog.ShowDialog(this) == DialogResult.OK) {
					try {
						ShaderLibraryConfiguration library = new ShaderLibraryConfiguration();

						// Store library path
						library.ConfigurationPath = fileDialog.FileName;
						// Save a new library
						ShaderLibraryConfiguration.Save(library);
						// Collect library
						mCurrentProject.AddLibrary(library);

						// Update UI
						UpdateModulesTree();
					} catch (Exception exception) {
						MessageBox.Show(String.Format("Unable to create shader library to {0}: {1}.", fileDialog.FileName, exception.Message));
					}
				}
			}
		}

		private void MenuOpenLibrary_Click(object sender, EventArgs e)
		{
			using (FileDialog fileDialog = new OpenFileDialog()) {
				fileDialog.Title = "Open shader library...";
				fileDialog.Filter = "Shader library (*.xml)|*.xml|All files|*.*";
				fileDialog.AddExtension = true;
				fileDialog.CheckFileExists = true;

				if (fileDialog.ShowDialog(this) == DialogResult.OK) {
					try {
						// Load and collect a shader library
						mCurrentProject.AddLibrary(ShaderLibraryConfiguration.Load(fileDialog.FileName));
						// Set project changed status
						mCurrentProjectChanged = true;

						// Update UI
						UpdateMenuBar();
						UpdateModulesTree();
					} catch (Exception exception) {
						MessageBox.Show(String.Format("Unable to load shader library from {0}: {1}.", fileDialog.FileName, exception.Message));
					}
				}
			}
		}

		#endregion

		#region Editor Context Menu Action Implementations

		private void MenuSaveShaderSource_Click(object sender, EventArgs e)
		{
			ToolStripItem menuItem = (ToolStripItem) sender;
			TabPage objectPage = (TabPage)menuItem.Owner.Tag;
			ShaderTextBox shaderTextBox = (ShaderTextBox) objectPage.Controls[0];
		}

		private void MenuCloseShaderSource_Click(object sender, EventArgs e)
		{
			ToolStripItem menuItem = (ToolStripItem) sender;
			TabPage objectPage = (TabPage)menuItem.Owner.Tag;
			
			ShaderEditorTabs.TabPages.Remove(objectPage);
		}

		#endregion

		#region Directory Context Menu Action Implementation

		private void MenuCreateDir_Click(object sender, EventArgs e)
		{
			ToolStripMenuItem menuItem = (ToolStripMenuItem) sender;
			ToolStrip menu = menuItem.Owner;
			TreeNode node = ModulesTree.GetNodeAt(ModulesTree.PointToClient(menu.Location));
			System.Diagnostics.Debug.Assert(node != null);

			TreeNode libraryNode = GetShaderLibraryNode(node);
			System.Diagnostics.Debug.Assert(libraryNode != null);
			System.Diagnostics.Debug.Assert(libraryNode.Tag is ShaderLibraryConfiguration);

			ShaderLibraryConfiguration libraryConfiguration = (ShaderLibraryConfiguration) libraryNode.Tag;
			string dirProjectPath = null;

			if (node.Tag is String)
				dirProjectPath = (string) node.Tag;
			if (node.Tag is ShaderLibraryConfiguration)
				dirProjectPath = String.Empty;

			// Append a node to the selected node, and enter in label edit
			if (dirProjectPath != null) {
				// Create a new directory node
				TreeNode dirNode = GetShaderTreeNode(libraryNode, Path.Combine(dirProjectPath, "New Directory\\"));
				// Label edit on new node
				dirNode.BeginEdit();
			}
		}

		private void MenuCreateModule_Click(object sender, EventArgs e)
		{
			
		}

		private void MenuRenameDir_Click(object sender, EventArgs e)
		{
			ToolStripMenuItem menuItem = (ToolStripMenuItem) sender;
			ToolStrip menu = menuItem.Owner;
			TreeNode node = ModulesTree.GetNodeAt(ModulesTree.PointToClient(menu.Location));

			node.BeginEdit();
		}

		private void MenuRemoveDir_Click(object sender, EventArgs e)
		{

		}

		private void MenuDeleteDir_Click(object sender, EventArgs e)
		{

		}

		#endregion

		#region Form Overrides

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
		protected override void Dispose(bool disposing)
		{
			if (disposing && (components != null)) {
				components.Dispose();
			}
			if (disposing) {
				foreach (KeyValuePair<RenderContext.GLVersion,RenderContext> pair in mContexts) {
					pair.Value.Dispose();
				}
			}
			base.Dispose(disposing);
		}

		#endregion

		private void MainWindow_Load(object sender, EventArgs e)
		{

		}
	}
}